require 'zip/zipfilesystem'
require 'odf2text' # in 'lib' directory
require 'unescapeHTML'
# Files in the database are represented by Myfile.
# It's called Myfile, because File is a reserved word.
# Files are in (belong to) a folder and are uploaded by (belong to) a User.
class Myfile < ActiveRecord::Base
  acts_as_ferret({:remote => true, :store_class_name => true, :fields => { :text => { :store => :yes }, :filename => { :store => :no }, :tag_list => { :store => :yes }, :user_id => { :store => :yes }, :folder_id => { :store => :yes }, :version => {:store => :yes}}},{:analyzer => Ferret::Analysis::StandardAnalyzer.new(stop_words = nil, lower=true)})

  acts_as_taggable
  
  belongs_to :folder
  belongs_to :user
  has_many :usages, :dependent => :destroy

  validates_uniqueness_of :filename, :scope => [:folder_id, :version]
  validates_presence_of :filename

  # Accessor that receives the data from the form in the view.
  # The file will be saved in a folder called 'uploads'.
  # (See: AWDWR pp. 362.)
  def myfile=(myfile_field)
    if myfile_field and myfile_field.length > 0
      # Get the filename
      filename = Myfile.base_part_of(myfile_field.original_filename)

      # Set date_time_created,
      # this will be the files temporary name.
      # (this instance variable is also used in temp_path)
      @date_time_created = Time.now.to_f

      # Save the file on the file system
      read_bytes=0
      File.open(self.temp_path, 'wb') do |f|
        while (buff = myfile_field.read(4096)) and (( read_bytes += 4096 ) <= (MAX_UPLOAD_FILE_SIZE || 1e17 )) # Increase exponent here and below, if you want to upload files of more than a few petabytes each. You lucky bastard :)
          f.write(buff)
        end
      end

      begin
        index_content(filename)
      rescue Exception
      end

      # Save it all to the database
      self.filename = read_bytes <= (MAX_UPLOAD_FILE_SIZE || 1e17) ? filename : filename.concat('.part')
      self.filesize = (File.size(self.temp_path) / 1024.0).ceil
    end
  
  end
  
  # Instead of getting a file from the client, link to a local file
  # NOTE: platform dependent!
  def hardlink_to(path)
    filename = File.basename(path)
    @date_time_created = Time.now.to_f
    File.link path, self.temp_path
    
    begin
      index_content(filename)
    rescue Exception
    end
    
    self.filename = filename
    self.filesize = (File.size(path) / 1024.0).ceil
  end
  
  #create a new doc from an email
  def from_email(email)
    filename=email.subject + '(' + email.date.strftime('%Y%m%d[%H:%M:%S]') + ').txt'
    @date_time_created = Time.now.to_f
    
    #save file
    File.open(self.temp_path,'wb') do |f|
      f.write(email.body)
    end
    
    self.indexed = true if self.text = email.body
    
    #save into DB
    self.filename = filename
    self.filesize = (email.body.length / 1024.0).ceil
    
  end
  
  attr_writer :text # Setter for text

  # Getter for text.
  # If text is blank get the text from the index.
  def text
    @text = Myfile.aaf_index.ferret_index[self.document_number][:text] if @text.blank?
  end

  after_create :rename_newfile, :add_thumbnail
  
  # The file in the uploads folder has the same name as the id of the file.
  # This must be done after_create, because the id won't be available any earlier.
  def rename_newfile
    definitive=self.path
    definitive+='.virus' if self.tag_list.include?(AV_WARNING) # this makes marked file inaccessible, a good things for suspects
    File.rename self.temp_path, definitive
  end
  
  # makes a hard link so a file with proper name and extension in a server accessible place is downloaded directly from the server.
  def mklink
    rel_dir=self.rel_dir
    abs_dir=self.abs_dir
    File.makedirs(abs_dir) # create if absent
    linkname=abs_dir+self.filename # note there will be more than one if file gets renamed. TODO:fix
    File.link(self.path,linkname) unless File.exist?(linkname)
    return rel_dir+self.filename
  end
  
  
  # create a thumbnail file with the configured helper
  def add_thumbnail
    return if self.tag_list.include?(AV_WARNING)
    return unless INDEX_THUMB_EXT.include?(File.extname(self.filename).downcase) 
    if HELPER_THUMBNAILS == 'RMagick' && Magick
      thumb=Magick::Image.read(self.path).first
      thumb.thumbnail!(THUMB_ATTRS[:width],(THUMB_ATTRS[:width]*thumb.rows) / thumb.columns)
      self.update_attributes(:thumbnailed => true) if thumb.write(self.thumb_path)
    end
    if MiniMagick
      thumb=MiniMagick::Image.from_file(self.path)
      thumb.thumbnail(THUMB_ATTRS[:width].to_s)
      self.update_attributes(:thumbnailed => true) if thumb.write(self.thumb_path)
      thumb.tempfile.delete
    end
    File.chmod(0644,self.thumb_path) if File.exist?(self.thumb_path)
  end

  # Scans file for viruses
  # Gets called just before indexing content
  def av_scan
    return unless AV_CMD
    self.tag_list.add(AV_WARNING) unless system(AV_CMD.gsub('%File', self.temp_path))
  end

  before_destroy :delete_file_on_disk
  # When removing a myfile record from the database,
  # the actual files on disk have to be removed too.
  # That is exactly what this method does.
  def delete_file_on_disk
    [self.path, self.path + '.virus', self.thumb_path].each do |path|
      File.delete(path) if File.exist?(path)
    end
    FileUtils.rm_rf(self.abs_dir) if File.exist?(self.abs_dir)
  end

  # Strip of the path and replace all the non alphanumeric,
  # underscores and periods in the filename with an underscore.
  def self.base_part_of(file_name)
    # NOTE: File.basename doesn't work right with Windows paths on Unix
    # INCORRECT: just_filename = File.basename(file_name.gsub('\\\\', '/')) 
    # get only the filename, not the whole path
    name = file_name.gsub(/^.*(\\|\/)/, '')

    # finally, replace all non alphanumeric, underscore or periods with underscore
    name.gsub(/[^\w\.\-]/, '_') 
  end

  # Returns the location of the file before it's saved
  def temp_path
    "#{UPLOAD_PATH}/#{@date_time_created}"
  end

  # The path of the file
  def path
    "#{UPLOAD_PATH}/#{self.id}"
  end
  
  # Path of thumbnail if one was generated
  def thumb_path
    "#{RAILS_ROOT}/public/images/boxthumbs/#{self.id}.jpg" #the file extension determines the format
  end
  
  # URL of thumbnail as seen by webserver
  def thumb_url
    "/images/boxthumbs/#{self.id}.jpg"
  end
  
  def rel_dir
    "/#{self.id}.alias/"
  end
  
  def abs_dir
    DOWNLOAD_DOCUMENT_ROOT.chomp('/') + self.rel_dir 
  end
  
  # All versions of a file
  def self.versions(myfile)
    find( :all, :conditions => {:folder_id => myfile.folder_id, :filename => myfile.filename}, :order => 'version DESC')
  end
  
  private
  # Get file content and add to index using helpers if AV scan succeeds
  def index_content(filename)
    self.av_scan
    return if self.tag_list.include?(AV_WARNING) # do not attempt scanning files with viruses
        # Variable to hold the plain text content of the uploaded file
    text_in_file = nil

    # Try to get the text from the uploaded file
    case filename
      when /\.txt$/
        text_in_file = File.open(self.temp_path) { |f| f.read }

      when /\.(s|x)?htm(l|ls)?$/ # get the file, strip all <> tags, convert entities
         text_in_file = File.open(self.temp_path) { |f| CGI::unescapeHTML(f.read.gsub(/<head>.*?<\/head>/m,'').gsub(/<.*?>/, ' ')) }

      when /\.sxw$|\.odt$|\.ods$/ # read content.xml from zip file, parse
        text_in_file = ReadOpenOffice.new(self.temp_path).text
    end

    # If it didn't get caught yet, try the helpers
    if text_in_file.blank?
      INDEX_HELPERS.each do |index_helper| # defined in environment.rb
        if filename =~ index_helper[:ext] # a matching helper!   

          if index_helper[:file_output] # a file that writes to an output file
            `#{ sprintf(index_helper[:helper], self.temp_path, self.temp_path + '_copy') }`
            text_in_file = File.open(self.temp_path + '_copy') { |f| f.read }
            File.delete(self.temp_path + '_copy')
          else # we get the contents from stido directly
            text_in_file = `#{ sprintf(index_helper[:helper], self.temp_path) }`
          end

          # Check if we need to remove first part (e.g. unrtf)
          unless index_helper[:remove_before].blank?
            if index_helper[:remove_before].match(text_in_file)
              text_in_file = Regexp::last_match.post_match 
            end
          end

          # Check if we need to remove last part
          unless index_helper[:remove_after].blank?
            if index_helper[:remove_after].match(text_in_file)
              text_in_file = Regexp::last_match.pre_match
            end
          end
        end
      end
    end

    unless text_in_file.blank?
      self.text = text_in_file.strip # assign text_in_file to self.text to get it indexed
      self.indexed = true
    end
  end
  
end